import { extend } from "../shared";

class ReactiveEffect {

    private _fn: () => void | any;
    private scheduler?: Function;
    private onStop?: () => void;
    private deps: Set<Set<ReactiveEffect>> = new Set();
    private active = true;

    constructor(fn: () => void | any, scheduler?: Function) {
        this._fn = fn;
        this.scheduler = scheduler;
    }

    getDeps() {
        return this.deps
    }

    getScheduler() {
        return this.scheduler;
    }

    run() {
        activeEffect = this
        return this._fn();
    }

    stop() {
        if (this.active) {
            cleanupEffect(this)
            this.active = false
            this.onStop && this.onStop()
        }
    }
}

function cleanupEffect(effect) {

    effect.deps.forEach(dep => {
        dep.delete(effect)
    })
}

export interface ReactiveEffectOptions {
    scheduler?: Function
    allowRecurse?: boolean
    onStop?: () => void
}

/**
 * 收集 target的所有依赖
 */
let targetMap = new WeakMap<object, Map<string, Set<ReactiveEffect>>>();

let activeEffect: ReactiveEffect | undefined;

export function effect(fn: () => void | any, options: ReactiveEffectOptions = {}) {
    // 首次需要调用fn
    const _effect = new ReactiveEffect(fn, options?.scheduler);
    extend(_effect, options);
    _effect.run();

    const runner = _effect.run.bind(_effect);
    // @ts-ignore
    runner.effect = _effect;

    return runner;
}

export function stop(runner) {
    runner.effect.stop();
    console.log('stop effect:', runner.effect, activeEffect);
}

export function trigger<T extends object>(target: T, key: string) {
    const depsMap = targetMap.get(target)
    const deps = depsMap?.get(key)
    console.log(`[trigger] ${key}, effects count:`, deps?.size); // 👈 日志
    deps && deps.forEach(effect => {
        if (effect.getScheduler()) {
            effect.getScheduler()?.call(effect)
        } else {
            effect.run()
        }

    })
}

export function track<T extends object>(target: T, key: string) {

    let depsMap = targetMap.get(target)

    if (!depsMap) {
        depsMap = new Map<string, Set<ReactiveEffect>>();
        targetMap.set(target, depsMap);
    }

    let deps = depsMap.get(key)
    if (!deps) {
        deps = new Set<ReactiveEffect>();
        depsMap.set(key, deps);
    }
    if (activeEffect && !deps.has(activeEffect)) {
        deps.add(activeEffect);
        activeEffect.getDeps().add(deps);
        console.log('[track] add effect to', key); // 👈 日志
    }
}
